/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO

Authors: Nitay Megides
		 Rachel Banino

Project: Exercise 1

Using: LinkedList.h
	   General.h
	   Hw1Types.h
	   Logger.h

Description:
The inventory list is a module encapsulates a linked list containing, logically, the 
inventory of the shop. The main data structure of this module is the variable 
inventoryList, which is linked list containing TInventoryItem pointers. Each item in 
the list has a pointer to another list of TInventoryProduct. See the diagram for details

    /-------------------\       /-------------------\       /-------------------\
    |  Tables			|       |  Chairs			|       |  Desks			| 
==>	|  ProductID = 1234	|  ==>  |  ProductID = 1888	|  ==>  |  ProductID = 2119	|  ==>
	|  Products			|       |  Products			|       |  Products			|
	\-----|-------------/       \-----|-------------/       \-----|-------------/
		  |                    		  |                     	  |
	/-------------\            	/-------------\             /-------------\
	|    Table	  |            	|    Chair	  |             |    Desk	  |
	| Price = 200 |            	| Price = 150 |             | Price = 102 |
	\-------------/            	\-------------/             \-------------/
		  |                    		  |                     	  |
	/-------------\            	/-------------\             /-------------\
	|    Table	  |            	|    Chair	  |             |    Desk	  |
	| Price = 400 |            	| Price = 160 |             | Price = 754 |
	\-------------/            	\-------------/             \-------------/


Notice that:
- The product IDs are sorted from low to high
- The products themselves are also sorted from the low price to the high
oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/


#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "InventoryList.h"
#include "Logger.h"

// The local list pointer
static PList inventoryList;



BOOL InitializeInventoryList()
{
	inventoryList = InitializeList();
	return (BOOL)(inventoryList != NULL);
}

PInventoryItem CreateInventoryItem(TProductID productID)
{
    PInventoryItem item = (PInventoryItem)malloc(sizeof(TInventoryItem));

    AssertOutOfMemory(item);
    
	// Initialize the product list values
	item->ProductID = productID;
	item->Products = InitializeList();

    return item;
}

BOOL AddInventoryProduct(const TProductID productID, const TMoney price)
{
    PListItem iteratedProductGroupListItem = inventoryList->Anchor;
    PListItem productGroupListItem = NULL;
    PInventoryItem productGroup = NULL;
    PInventoryItem iteratedproductGroup = NULL;
    PListItem productListItem = NULL;
    PInventoryProduct product = NULL;
    PListItem iteratedProduct = NULL;
    
    // First find the InventoryItem corresponding to the productID param
    while (iteratedProductGroupListItem != NULL) 
	{
        iteratedproductGroup = (PInventoryItem)iteratedProductGroupListItem->Value;

        // Check if we found an existing product id
        if (iteratedproductGroup->ProductID == productID)
        {
			productGroupListItem = iteratedProductGroupListItem;
            productGroup = iteratedproductGroup;
            break;
        }

        // Now check if we passed our place in the product IDs
        if (iteratedproductGroup->ProductID > productID)
        {
            // Whoops! we passed our spot
            // Create a new inventory item
			productGroupListItem = CreateValue(CreateInventoryItem(productID));	
			productGroup = (PInventoryItem)productGroupListItem->Value;

            // Insert it
			InsertItemBefore(inventoryList, iteratedProductGroupListItem, productGroupListItem);
            

            // Aaaaand we're done
            break;
        }

        // Check the next item.
        iteratedProductGroupListItem = GetNext(iteratedProductGroupListItem);
	}
    // If we've reached the end of the list but we didn't find a nice place for this productID
    if (productGroupListItem == NULL)
    {
        // Create & add a new inventory item
		productGroupListItem = AddValue(inventoryList, CreateInventoryItem(productID));
        productGroup = (PInventoryItem)productGroupListItem->Value;
    }

    // At this point we have our product group in productGroup
    // We want to add the new product (which costs price) and leave the list sorted
    // We do this the same way we did in the previous loop

    // First create a new product entry
    product = (PInventoryProduct)malloc(sizeof(TInventoryProduct));
    AssertOutOfMemory(product);
    productListItem = CreateValue(product);
	product->Price = price;
	product->ProductGroup = productGroup;
	product->ListItem = productListItem;

    // Now let's find where to put it
    iteratedProduct = productGroup->Products->Anchor;
    while (iteratedProduct != NULL)
    {
        if (((PInventoryProduct)iteratedProduct->Value)->Price >= price)
        {
            // Insert it
            return InsertItemBefore(productGroup->Products, iteratedProduct, productListItem);
            
            break;
        }

        // Check the next item.
        iteratedProduct = GetNext(iteratedProduct);
	}
    // If we've reached the end of the list but we didn't find a nice place for this productID
    if (iteratedProduct == NULL)
    {
        // Insert it
        return (BOOL)(AddItem(productGroup->Products, productListItem) == productListItem);
    }

    return FALSE;
}

PInventoryItem FindInventoryItem(const TProductID productID)
{
    PListItem inventoryListItem = inventoryList->Anchor;
	PInventoryItem inventoryItem;
	
	while (inventoryListItem != NULL)
	{
		// We rely on that the list is sorted by price (low to high)
		inventoryItem = (PInventoryItem)inventoryListItem->Value;

        // We found our product
		if (inventoryItem->ProductID == productID)
            break;

        inventoryListItem = GetNext(inventoryListItem);
    }

	// I'm returning the list item value instead on inventoryItem to return NULL 
	// when the productID wasn't found
    if (inventoryListItem == NULL)
        return NULL;

	return (PInventoryItem)inventoryListItem->Value;
}

PInventoryProduct GetInventoryProduct(const TProductID productID, const TMoney highestPrice)
{
	PInventoryItem inventoryItem = FindInventoryItem(productID);
	PListItem inventoryProductListItem = NULL;
	PInventoryProduct inventoryProduct = NULL;
	PInventoryProduct closestPriceProduct = NULL;
	if (inventoryItem == NULL)
    {
        // Product ID doesn't exist. 
        return NULL;
    }

	// Init the loop
	inventoryProductListItem = inventoryItem->Products->Anchor;

	while (inventoryProductListItem !=  NULL)
	{
		inventoryProduct = (PInventoryProduct)inventoryProductListItem->Value;
		// If we're past our price - Break
		if (inventoryProduct->Price >= highestPrice)
			break;

		closestPriceProduct = inventoryProduct;
		inventoryProductListItem = GetNext(inventoryProductListItem);
	}
	
	return closestPriceProduct;
}

void RemoveInventoryProduct(const TProductID productID, const TMoney price)
{
    // The +1 is to get a product that costs *price* (GetInventoryProduct returns the 
    // closest product in price, from below)
	PInventoryProduct product = GetInventoryProduct(productID, price + 1);
	PInventoryItem productGroup = NULL;
	
	if (product == NULL)
	{
		// Product does not exist. Do nothing as this might not be an error
		return;
	}

	productGroup = product->ProductGroup;

	// Do the actual removing - This frees the list item, and the product
	RemoveItem(productGroup->Products, product->ListItem);
}

void PrintInventoryList()
{
	PListItem currentInventoryListItem = NULL;
    PInventoryItem currentInventoryItem = NULL;
    PListItem currentProductListItem = NULL;
    PInventoryProduct currentProduct = NULL;

	int counter = 0;
    int sizeOfFile = 0;


    // For each product group, write each product
    currentInventoryListItem = inventoryList->Anchor;
    while (currentInventoryListItem != NULL)
    {
		counter = 0;
        currentInventoryItem = (PInventoryItem)currentInventoryListItem->Value;

        // Write the product ID
        printf(
			"Product ID %d (%d products):\n", 
			currentInventoryItem->ProductID, 
			currentInventoryItem->Products->Count);

        // Write all the products
        currentProductListItem = currentInventoryItem->Products->Anchor;
        while (currentProductListItem != NULL)
        {
            currentProduct = (PInventoryProduct)currentProductListItem->Value;

            printf("%d. Price: %2.3f\n", ++counter, currentProduct->Price);

            currentProductListItem = GetNext(currentProductListItem);
        }

		printf("\n");
        // Move on to the next item
        currentInventoryListItem = GetNext(currentInventoryListItem);
    }
}

void SaveInventoryList(const char* filename)
{
    FILE* file;
    PListItem currentInventoryListItem = NULL;
    PInventoryItem currentInventoryItem = NULL;
    PListItem currentProductListItem = NULL;
    PInventoryProduct currentProduct = NULL;

    int sizeOfFile = 0;

    // First open the file
    AssertFileOpenError(file = fopen(filename, "wb"));

    // Now start the writing

    // 1. The number of product groups we're writing
    WriteAndAssert(file, &(inventoryList->Count), sizeof(int));

    // 2. For each product group, write each product
    currentInventoryListItem = inventoryList->Anchor;
    while (currentInventoryListItem != NULL)
    {
        currentInventoryItem = (PInventoryItem)currentInventoryListItem->Value;

        // Write the product ID
        WriteAndAssert(file, &(currentInventoryItem->ProductID), sizeof(TProductID));

        // Now write how many products we have
        WriteAndAssert(file, &(currentInventoryItem->Products->Count), sizeof(int));

        // Write all the products
        currentProductListItem = currentInventoryItem->Products->Anchor;
        while (currentProductListItem != NULL)
        {
            currentProduct = (PInventoryProduct)currentProductListItem->Value;

            WriteAndAssert(file, &(currentProduct->Price), sizeof(TMoney));

            currentProductListItem = GetNext(currentProductListItem);
        }

        // Move on to the next item
        currentInventoryListItem = GetNext(currentInventoryListItem);
    }

    fclose(file);
}

void LoadInventoryList(const char* filename)
{
	FILE* file = NULL;
	int fileSize = -1;
	void* fileBuffer = NULL;

	int productGroupCount = 0;
	int i = 0, j= 0;
	TProductID productGroupID = 0;
	int productGroupSize = 0;
	TMoney productPrice = 0;

	// There are basically 2 options to do this:
	// 1. Typecast a well-structured struct onto a void* buffer read from the file
	// 2. Read the variables one by one
	// The first option is faster, but the second is more secure and easier to 
	// debug in my opinion. That is the way this function works.

	// Open the file and verify beginning
    AssertFileOpenError(file = fopen(filename, "rb"));
	rewind(file);

	// Get the number of groups
	ReadAndAssert(file, &productGroupCount, sizeof(int));

	for (i = 0; i < productGroupCount; i++)
	{
		// Get the product group ID and the size of the group
		ReadAndAssert(file, &productGroupID, sizeof(TProductID));
		ReadAndAssert(file, &productGroupSize, sizeof(int));

		for (j = 0; j < productGroupSize; j++)
		{
			ReadAndAssert(file, &productPrice, sizeof(TMoney));

			AddInventoryProduct(productGroupID, productPrice);
			LogAddInventoryFromFile(productGroupID, productPrice);
		}
	}

	fclose(file);
}

void FreeInventoryList()
{
	PListItem inventoryListItem = inventoryList->Anchor;
	PInventoryItem inventoryItem = NULL;
	
	// Loop on all items
	while (inventoryListItem != NULL)
	{
		inventoryItem = (PInventoryItem)inventoryListItem->Value;

		// This should take care of the product list and the products themselves
		FreeList(inventoryItem->Products);

		// Next item
		inventoryListItem = GetNext(inventoryListItem);
	}

	FreeList(inventoryList);
}
